/**
 * 
 */
package org.ycsoft.MetroidReader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;


/**
 * Represents a metroid file stream.
 * @author allad
 *
 */
public class MetroidStream extends RandomAccessFile {
	String filename;
	
    public static final int CR = 13;
    public static final int LF = 10;
    public static final int MAX_LINE = 255;
    public static final char[] DELIMITERS = new char[]{Commons.SEPARATOR};
	
	public MetroidStream(String filename) throws IOException{
		super(filename,"r");
	}
	
	public void seek(long offset,SeekOrigin origin) throws IOException{
		if(origin == SeekOrigin.BEGIN){
			seek(offset);
		}
		else if(origin == SeekOrigin.CURRENT){
			long current = getFilePointer();
			seek(current+offset);
		}
		else if(origin == SeekOrigin.END){
			long total = length();
			seek(offset+total);
		}
	}
	
	
    /**
     * Read the byte just preceding the current position.
     * The cursor is then positioned just before that byte
     * @return the byte read or -1 if start of file
     */
    public int readPreviousByte() throws IOException
    {
        if (getFilePointer() == 0)
            return -1;

        int retVal;
        //go one byte before
        try {
        	seek(-1, SeekOrigin.CURRENT);
        	retVal = read();
            seek(-1, SeekOrigin.CURRENT);
        }catch(IOException ie){
        	retVal = -1;
        }   

        return retVal;
    }
    
    
    /**
     * Read the line backward up to the previous EOL
     * the EOL is consumed
     * @param outMs
     * @return
     * @throws IOException
     */
    public int readPreviousLine(ByteArrayOutputStream outMs)throws IOException
    {
        int c;  //character read
        int n = 0;   //number of caracters read
        ByteArrayOutputStream ms = new ByteArrayOutputStream();

        c = this.readPreviousByte();
        while (c != -1 && c != LF && c != CR)
        {
            byte[] buffer = ms.toByteArray();

            ms = new ByteArrayOutputStream(MAX_LINE);
            ms.write((byte)c);
            ms.write(buffer, 0, buffer.length);
           
            n++;
            c = this.readPreviousByte();
        }
        
        ms.writeTo(outMs);

        if (c == -1 && n == 0) //EOF
            throw new IOException("Start of stream reached. No previous line available.");

        //if c is CR, check if the next caracter is LF
        //and consume it if so
        if (c == LF)
        {
            int t = this.readPreviousByte();
            if( t == -1)
                throw new IOException("Start of stream reached. No previous line available.");

            if (t != CR)//if it's not an LF, seek back to the previous character
                seek(1, SeekOrigin.CURRENT);
        }
        return n;
    }
    
  
    
    /**
     * Return the bytes from the stream up to a EOL caracter
     * the EOL caracter is not returned.
     *
     * At most len bytes are read.
     * In len bytes are read before EOL, then it doesn't consume the rest of the line.
     * @param ms
     * @return
     * @throws IOException
     */
    public int readLineBytes(ByteArrayOutputStream ms) throws IOException{
    	int c;  //character read
        int n = 0;   //number of caracters read

        c = this.read();
        while ( c != -1 && c != LF && c != CR )
        {
            //buffer[offset + n] = (byte)c;
            ms.write((byte)c);
            n++;
            c = this.read();
        }
       
        if (c == -1 && n == 0) //EOF
            throw new  IOException("End of stream reached");

        //if c is CR, check if the next caracter is LF
        //and consume it if so
        if (c == CR)
        {
            int t = this.read();

            if (t == -1)
            {
                //the CR is just at the end of the file
                throw new IOException("End of stream reached");
            }

            if (t != LF)//if it's not an LF, seek back to the previous character
                seek(-1, SeekOrigin.CURRENT);
        }
        return n;
    }
    
    /**
     * Read the line  up to the next EOL
     * the EOL is consumed
     * @return
     */
    public String readLineUTF() throws IOException{
    	ByteArrayOutputStream ms = new ByteArrayOutputStream(MAX_LINE);

        int n = this.readLineBytes(ms);

        return new String(ms.toByteArray(),Commons.DEFAULT_ENCODING);

    }
    
    /**
     * Read the line backward up to the previous EOL
     * the EOL is consumed
     * @return
     */
    public String readPreviousLineAsString()throws IOException
    {
    	ByteArrayOutputStream ms = new ByteArrayOutputStream(MAX_LINE);

        int n = this.readPreviousLine(ms);

        return new String(ms.toByteArray(),Commons.DEFAULT_ENCODING);

    }
    
    
    /**
     * Returns true if i is a whiteSpace character
     * @param i
     * @return
     */
    public static boolean isWhiteSpace(int i)
    {
        //add some checking for some characters 
        //forgotter by the .Net implementation
        if (i == 0 )//NULL
            return true;

        return Character.isWhitespace(i);
    }


    
    /**
     * Returns true if the caracter which codepoint is a Metroid separator
     * @param i
     * @return
     */
    public static boolean isDelimiter(int i)
    {
        char c = Character.toChars(i)[0];

        for(int j=0;j<DELIMITERS.length;j++)
        {
        	char car = DELIMITERS[j];
            if (car == c)
                return true;
        }
        return false;
    }
}
